package com.xxkk.utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Mysql： sql语句执行工具
 * 创建数据连接池，并接收sql语句执行后返回结果
 * 目的：实现连接多个mysql数据库，所以提供函数创建多个数据库连接池
 *
 * @author 小袖空空
 * @since 2025-10-13
 */
public class SqlExecutive {
    private static final Logger log = Logger.getLogger(SqlExecutive.class.getName());
    public List<DataSource> sources = new ArrayList<>();//直接按添加顺序的下标取用 index -> sources.get(index);

    public DataSource addDataSource(DataSource ds) {
        synchronized (this) {
            sources.add(ds);
        }
        log.info("添加数据连接池 -> " + ds.toString());
        return ds;
    }

    /**
     * 添加连接池
     *
     * @param url  连接地址
     * @param user 用户名
     * @param pwd  密码
     * @return 连接池 {@link DataSource}
     */
    public DataSource addDataSource(String url, String user, String pwd) {
        ComboPooledDataSource ds = new ComboPooledDataSource();//import com.mchange.v2.c3p0.ComboPooledDataSource; 或其它数据库连接池也可替代
        ds.setJdbcUrl(url);
        ds.setUser(user);
        ds.setPassword(pwd);
        synchronized (this) {
            sources.add(ds);
        }
        log.info("添加数据连接池 -> " + ds.toString(true));
        return ds;
    }

    /**
     * 查询语句 查找表数据
     *
     * @param sql   例子： select * from table where key=? and vla=?;
     * @param clazz 映射类想
     * @param args
     * @param <T>
     * @return
     */
    public <T> List<T> query(String sql, Class<T> clazz, String... args) {
        return query(this.getSource(0), sql, clazz, args);
    }

    public <T> List<T> query(DataSource ds, String sql, Class<T> clazz, String... args) {
        try (Connection conn = ds.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) {
            int i = 0;
            for (String arg : args)
                stmt.setString(++i, arg);
            ResultSet rs = stmt.executeQuery();
            return map(rs, clazz);
        } catch (SQLException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
        }
        return null;
    }

    public DataSource getSource(int index) {
        if (this.sources.isEmpty() || index >= this.sources.size()) {
            log.warning("数据连接池不存在 -> size:" + this.sources.size() + " index:" + index);
            return null;
        }
        return this.sources.get(index);
    }

    /**
     * sql更新语句执行
     * <p><b>例子：</b></p>
     * <pre>{@code
     * INSERT INTO users(username, email, created_at) VALUES('john_doe', 'john@example.com', CURRENT_TIMESTAMP);
     * UPDATE users  SET email = 'new_email@example.com'  WHERE user_id = 101;
     *  }</pre>
     *
     * @param sql  更新语句
     * @param args 格式化字符串参数
     * @return 影响行数
     * @see PreparedStatement#executeUpdate
     */
    public int update(String sql, String... args) {
        return this.update(this.getSource(0), sql, args);
    }

    public int update(DataSource ds, String sql, String... args) {
        try (Connection conn = ds.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) {
            int i = 0;
            for (String arg : args)
                stmt.setString(++i, arg);
            return stmt.executeUpdate();
        } catch (SQLException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
        }
        return 0;
    }

    /**
     * 将JDBC ResultSet结果集映射为指定类型的对象列表:ml-citation{ref="1,4" data="citationList"}
     *
     * <p>该方法通过反射自动匹配ResultSet列名与目标类字段名（不区分大小写），
     * 支持基本数据类型及其包装类的自动转换。若字段类型不匹配会抛出运行时异常:ml-citation{ref="2" data="citationList"}</p>
     *
     * <p><b>典型用法：</b></p>
     * <pre>{@code
     * List<User> users = map(rs, User.class);
     * }</pre>
     *
     * @param <T>   目标对象类型，必须是具体类且有无参构造方法:ml-citation{ref="3" data="citationList"}
     * @param rs    数据库查询结果集，必须处于有效状态（未关闭）
     * @param clazz 目标类的Class对象，用于反射实例化
     * @return 包含映射结果的List集合(ArrayList)，若结果集为空则返回空列表（非null）
     * @throws IllegalArgumentException 如果参数为null或clazz为接口/抽象类
     * @throws RuntimeException         当字段映射失败或数据库访问异常时抛出
     * @see java.sql.ResultSet
     * @see java.lang.reflect.Field
     * @since 1.0
     */
    public static <T> List<T> map(ResultSet rs, Class<T> clazz) throws SQLException {
        List<T> result = new ArrayList<>();
        try {
            if (rs == null || clazz == null || rs.isClosed())
                return result;
            ResultSetMetaData meta = rs.getMetaData();
            Field[] fields = clazz.getDeclaredFields();

            while (rs.next()) {
                T obj = clazz.getDeclaredConstructor().newInstance();
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    String colName = meta.getColumnLabel(i).toLowerCase();
                    for (Field field : fields)
                        if (field.getName().equalsIgnoreCase(colName)) {
                            field.setAccessible(true);
                            field.set(obj, rs.getObject(i));
                            break;
                        }
                }
                result.add(obj);
            }
        } catch (Exception e) {
            log.log(Level.SEVERE, "映射失败", e);
        } finally {
            assert rs != null;
            rs.close();
        }
        return result;
    }
}
